/**
 * SimpleReceiver MicroController program.
 *
 * Author: Jimmy F.Klarke, InfiniteSpace Studio
 */

#include <htc.h>
#include "sr.h"



__CONFIG(UNPROTECT & BOREN & MCLREN & PWRTEN & WDTEN & INTOSCIO);

/* Switch, Radio type, Model 2, Version 1 */
__IDLOC7('S', 'R', '2', '1');



/* Data EEPROM */
/* Channel 0 parameters. */
__EEPROM_DATA(0x04, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
__EEPROM_DATA(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);

/* Channel 0 commands. */
__EEPROM_DATA(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
__EEPROM_DATA(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);

/* Channel 1 parameters. */
__EEPROM_DATA(0x04, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
__EEPROM_DATA(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);

/* Channel 1 commands. */
__EEPROM_DATA(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
__EEPROM_DATA(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);

/* Global parameters. */
__EEPROM_DATA(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01);
__EEPROM_DATA(0x0B, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
__EEPROM_DATA(0xFF, 0x08, 0x04, 0x0C, 0x02, 0x0A, 0x06, 0xFF);
__EEPROM_DATA(0x01, 0x09, 0x05, 0xFF, 0x03, 0x0B, 0x07, 0xFF);

/* Default Channel 0 parameters. */
__EEPROM_DATA(0x04, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
__EEPROM_DATA(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);

/* Default Channel 1 parameters. */
__EEPROM_DATA(0x04, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
__EEPROM_DATA(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);



/* channel 0 option */
CH_OPTION c0op;
/* channel 1 option */
CH_OPTION c1op;

/* true if channel 0 selected, false if channel 1 */
bit isCh0;

/* swap channel 0 on command end */
bit swapCh0OnCmdEnd;
/* swap channel 1 on command end */
bit swapCh1OnCmdEnd;

/* state machine state */
unsigned char state;

/* received key */
unsigned char key;
/* command mapped from key */
unsigned char cmd;
/* new command begin */
bit cmdBegin;
/* new command end */
bit cmdEnd;
/* is key mapped to cmd? */
bit isNewKey;

/* sn verify position */
unsigned char snPos;
bit snOk;

/* is system command begin? */
bit inSysCmd;
/* system menu enter/exit cmd duration time counter */
unsigned char sysCmdTimeCount;



unsigned char isCh0On(void) {
    return (c0op.alwaysOnLine) ? !CTRL0 : CTRL0;
}

unsigned char isCh1On(void) {
    return (c1op.alwaysOnLine) ? !CTRL1 : CTRL1;
}

void loadCh0(void) {
    c0op = CH0_OPTION;
}

void storeCh0(void) {
    CH0_OPTION = c0op;
}

void loadCh1(void) {
    c1op = CH1_OPTION;
}

void storeCh1(void) {
    CH1_OPTION = c1op;
}

void ch0On(void) {
    CTRL0 = c0op.alwaysOnLine ? 0 : 1;
    if (c0op.saveLastState) {
        c0op.initSwitch = 1;
        storeCh0();
    }
}

void ch0Off(void) {
    CTRL0 = c0op.alwaysOnLine ? 1 : 0;
    if (c0op.saveLastState) {
        c0op.initSwitch = 0;
        storeCh0();
    }
}

void swapCh0(void) {
    CTRL0 = !CTRL0;
    if (c0op.saveLastState) {
        c0op.initSwitch = c0op.alwaysOnLine ? !CTRL0 : CTRL0;
        storeCh0();
    }
}

void ch1On(void) {
    CTRL1 = c1op.alwaysOnLine ? 0 : 1;
    if (c1op.saveLastState) {
        c1op.initSwitch = 1;
        storeCh1();
    }
}

void ch1Off(void) {
    CTRL1 = c1op.alwaysOnLine ? 1 : 0;
    if (c1op.saveLastState) {
        c1op.initSwitch = 0;
        storeCh1();
    }
}

void swapCh1(void) {
    CTRL1 = !CTRL1;
    if (c1op.saveLastState) {
        c1op.initSwitch = c1op.alwaysOnLine ? !CTRL1 : CTRL1;
        storeCh1();
    }
}

void pauseHalfSecond(void) {
    unsigned int i = 30000;
    while (--i);
}

void blinkRed(void) {
    LED_RED = !LED_RED;
    pauseHalfSecond();
    LED_RED = !LED_RED;
    pauseHalfSecond();
}



void init(void) {
    unsigned char i;

    /* init I/O port. */
    PORTA = 0x0;
    PORTC = 0x0;
    TRISA = PORTA_DIR;
    TRISC = PORTC_DIR;

    LED_GREEN = 0;
    LED_RED = 1;

    /* restore default parameters if MCLR reset. */
    if (POR && BOR && TO) {
        for (i = 0; i < CH_SIZE; ++i) {
            CH0[i] = DEFAULT_CH0[i];
            CH1[i] = DEFAULT_CH1[i];
        }
    }

    POR = 1;
    BOR = 1;

    /* set initialize switch state. */
    loadCh0();
    loadCh1();
    if (c0op.initSwitch) {
        ch0On();
    } else {
        ch0Off();
    }
    if (c1op.initSwitch) {
        ch1On();
    } else {
        ch1Off();
    }

    LED_RED = 0;
    LED_GREEN = 1;

    state = WAIT_CMD;

    /* initialize interrupts. */
    INTF = 0;
    INTE = 1;
    ei();
}

void onCmdBegin(void) {
    unsigned char i;
    unsigned char cmdSize;
    unsigned char cmdNext;

    if (state == WAIT_CMD) {
        LED_RED = 1;

        for (i = 0; i < CH0_CMD_SIZE; ++i) {
            switch (c0op.switchMode) {
            case 0b00:          /* mode 0 */
                if (cmd == CH0_CMDS[i*2]) {
                    ch0On();
                } else if (cmd == CH0_CMDS[i*2 + 1]) {
                    ch0Off();
                }
                break;
            case 0b01:          /* mode 1 */
                if (cmd == CH0_CMDS[i]) {
                    swapCh0();
                }
                break;
            case 0b10:          /* mode 2 */
                if (cmd == CH0_CMDS[i]) {
                    ch0On();
                    swapCh0OnCmdEnd = 1;
                }
                break;
            }
        }
        for (i = 0; i < CH1_CMD_SIZE; ++i) {
            switch (c1op.switchMode) {
            case 0b00:          /* mode 0 */
                if (cmd == CH1_CMDS[i*2]) {
                    ch1On();
                } else if (cmd == CH1_CMDS[i*2 + 1]) {
                    ch1Off();
                }
                break;
            case 0b01:          /* mode 1 */
                if (cmd == CH1_CMDS[i]) {
                    swapCh1();
                }
                break;
            case 0b10:          /* mode 2 */
                if (cmd == CH1_CMDS[i]) {
                    ch1On();
                    swapCh1OnCmdEnd = 1;
                }
                break;
            }
        }
    } else {
        blinkRed();
        if (state == VERIFY_SN) {
            if (cmd == 0xA) {
                cmd = 0x0;
            }
            if (cmd != SERIAL_NUMBER[snPos++]) {
                snOk = 0;
            }
            if (snPos >= SERIAL_NUMBER_SIZE) {
                blinkRed();
                if (snOk) {
                    blinkRed();
                } else {
                    LED_GREEN = 1;
                }
                state = MENU_MAIN;
            }
        } else if (snOk) {
            switch (state) {
            case MENU_MAIN:
                switch (cmd) {
                case CMD_CH0:
                    isCh0 = 1;
                    state = MENU_CHANNEL;
                    break;
                case CMD_CH1:
                    isCh0 = 0;
                    state = MENU_CHANNEL;
                    break;
                default:
                    blinkRed();
                    break;
                }
                break;

            case MENU_CHANNEL:
                switch(cmd) {
                case CMD_LEARN:
                    state = MENU_CH_LEARN;
                    break;
                case CMD_CLEAR:
                    if (isCh0) {
                        CH0_CMD_SIZE = 0;
                        CH0_CMD_NEXT = 0;
                    } else {
                        CH1_CMD_SIZE = 0;
                        CH1_CMD_NEXT = 0;
                    }
                    blinkRed();
                    blinkRed();
                    break;
                case CMD_CH_SLS:
                    state = MENU_CH_SLS;
                    break;
                case CMD_CH_ISS:
                    state = MENU_CH_ISS;
                    break;
                case CMD_CH_AOL:
                    state = MENU_CH_AOL;
                    break;
                case CMD_CH_MODE:
                    state = MENU_CH_MODE;
                    break;
                case CMD_BACK:
                    state = MENU_MAIN;
                    break;
                default:
                    blinkRed();
                    break;
                }
                break;

            case MENU_CH_LEARN:
                if (isCh0) {
                    cmdSize = CH0_CMD_SIZE;
                    cmdNext = CH0_CMD_NEXT;
                    CH0_CMDS[cmdNext] = cmd;
                    if (cmdSize < CH_CMD_SIZE) {
                        CH0_CMD_SIZE = ++cmdSize;
                    }
                    CH0_CMD_NEXT = (++cmdNext) % CH_CMD_SIZE;
                } else {
                    cmdSize = CH1_CMD_SIZE;
                    cmdNext = CH1_CMD_NEXT;
                    CH1_CMDS[cmdNext] = cmd;
                    if (cmdSize < CH_CMD_SIZE) {
                        CH1_CMD_SIZE = ++cmdSize;
                    }
                    CH1_CMD_NEXT = (++cmdNext) % CH_CMD_SIZE;
                }
                state = MENU_CHANNEL;
                blinkRed();
                blinkRed();
                break;

            case MENU_CH_SLS:
                switch (cmd) {
                case CMD_YES:
                    if (isCh0) {
                        c0op.saveLastState = 1;
                        storeCh0();
                    } else {
                        c1op.saveLastState = 1;
                        storeCh1();
                    }
                    state = MENU_CHANNEL;
                    blinkRed();
                    blinkRed();
                    break;
                case CMD_NO:
                    if (isCh0) {
                        c0op.saveLastState = 0;
                        storeCh0();
                    } else {
                        c1op.saveLastState = 0;
                        storeCh1();
                    }
                    state = MENU_CHANNEL;
                    blinkRed();
                    blinkRed();
                    break;
                default:
                    blinkRed();
                    break;
                }
                break;

            case MENU_CH_ISS:
                switch (cmd) {
                case CMD_CH_ON:
                    if (isCh0) {
                        c0op.initSwitch = 1;
                        storeCh0();
                    } else {
                        c1op.initSwitch = 1;
                        storeCh1();
                    }
                    state = MENU_CHANNEL;
                    blinkRed();
                    blinkRed();
                    break;
                case CMD_CH_OFF:
                    if (isCh0) {
                        c0op.initSwitch = 0;
                        storeCh0();
                    } else {
                        c1op.initSwitch = 0;
                        storeCh1();
                    }
                    state = MENU_CHANNEL;
                    blinkRed();
                    blinkRed();
                    break;
                default:
                    blinkRed();
                    break;
                }
                break;

            case MENU_CH_AOL:
                switch (cmd) {
                case CMD_YES:
                    if (isCh0) {
                        c0op.alwaysOnLine = 1;
                        storeCh0();
                    } else {
                        c1op.alwaysOnLine = 1;
                        storeCh1();
                    }
                    state = MENU_CHANNEL;
                    blinkRed();
                    blinkRed();
                    break;
                case CMD_NO:
                    if (isCh0) {
                        c0op.alwaysOnLine = 0;
                        storeCh0();
                    } else {
                        c1op.alwaysOnLine = 0;
                        storeCh1();
                    }
                    state = MENU_CHANNEL;
                    blinkRed();
                    blinkRed();
                    break;
                default:
                    blinkRed();
                    break;
                }
                break;

            case MENU_CH_MODE:
                switch (cmd) {
                case CMD_CH_SEP:
                    if (isCh0) {
                        c0op.switchMode = 0b00;
                        c0op.initSwitch = 0;
                        storeCh0();
                    } else {
                        c1op.switchMode = 0b00;
                        c1op.initSwitch = 0;
                        storeCh1();
                    }
                    state = MENU_CHANNEL;
                    blinkRed();
                    blinkRed();
                    break;
                case CMD_CH_FLIP:
                    if (isCh0) {
                        c0op.switchMode = 0b01;
                        c0op.initSwitch = 0;
                        storeCh0();
                    } else {
                        c1op.switchMode = 0b01;
                        c1op.initSwitch = 0;
                        storeCh1();
                    }
                    state = MENU_CHANNEL;
                    blinkRed();
                    blinkRed();
                    break;
                case CMD_CH_CLICK:
                    if (isCh0) {
                        c0op.switchMode = 0b10;
                        c0op.initSwitch = 0;
                        storeCh0();
                    } else {
                        c1op.switchMode = 0b10;
                        c1op.initSwitch = 0;
                        storeCh1();
                    }
                    state = MENU_CHANNEL;
                    blinkRed();
                    blinkRed();
                    break;
                default:
                    blinkRed();
                    break;
                }
                break;

            }
        }
    }

    if ((cmd == SYSTEM_MENU_CMD) || (cmd == SYSTEM_EXIT_CMD)) {
        inSysCmd = 1;
        sysCmdTimeCount = 0;
        CLRWDT();
    } else {
        inSysCmd = 0;
    }
}

void onCmdEnd(void) {
    if (state == WAIT_CMD) {
        LED_RED = 0;
    }

    if (swapCh0OnCmdEnd) {
        swapCh0();
        swapCh0OnCmdEnd = 0;
    }
    if (swapCh1OnCmdEnd) {
        swapCh1();
        swapCh1OnCmdEnd = 0;
    }

    inSysCmd = 0;
}



void main(void) {
    init();

    while(1) {
        SLEEP();

        if (WDT_TO()) {         // wakeup by watch-dog timeout
            if (inSysCmd && VT) {
                ++sysCmdTimeCount;
                if (sysCmdTimeCount >= SYS_CMD_TIME) {
                    inSysCmd = 0;
                    if (cmd == SYSTEM_MENU_CMD) {
                        state = VERIFY_SN;
                        snPos = 0;
                        snOk = 1;
                        blinkRed();
                        LED_GREEN = 0;
                    } else if (cmd == SYSTEM_EXIT_CMD) {
                        state = WAIT_CMD;
                        LED_RED = 0;
                        LED_GREEN = 1;
                    }
                }
            } else {
                inSysCmd = 0;
            }
        } else {                // wakeup by key press
            if (isNewKey) {
                cmd = KEY_CMD_MAP[key];
                isNewKey = 0;
            }
            if (cmdBegin) {
                onCmdBegin();
                cmdBegin = 0;
            }
            if (cmdEnd) {
                onCmdEnd();
                cmdEnd = 0;
            }
        }
    }
}

static void interrupt isr(void) {
    unsigned char k = PORTC & 0b1111;
    if (INTF) {
        if (INTEDG == 1) {
            key = k;
            isNewKey = 1;
            cmdBegin = 1;
            INTEDG = 0;
        } else {
            cmdEnd = 1;
            INTEDG = 1;
        }

        INTF = 0;
    }
}
